// Copyright (c) 2023 Institute of Software, Chinese Academy of Sciences
// Ripple is licensed under Mulan PSL v2.
// You can use this software according to the terms and conditions of the Mulan PSL v2.
// You may obtain a copy of Mulan PSL v2 at:
//          http://license.coscl.org.cn/MulanPSL2
// THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
// EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
// MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
// See the Mulan PSL v2 for more details.
package ripple.server.core.overlay.vector;

import java.util.ArrayList;
import java.util.List;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import ripple.common.DebugTool;
import ripple.common.entity.AbstractMessage;
import ripple.common.entity.NodeMetadata;
import ripple.server.core.overlay.Overlay;

public class VectorOverlay implements Overlay {

    private static final Logger LOGGER = LoggerFactory.getLogger(VectorOverlay.class);
    private List<NodeMetadata> nodeList;

    private int dimension;

    public VectorOverlay(int dimension) {
        this.setDimension(dimension);
    }

    public int getDimension() {
        return dimension;
    }

    public void setDimension(int dimension) {
        this.dimension = dimension;
    }

    private Coordinates coordinates;

    public Coordinates getCoordinates() {
        return coordinates;
    }

    public void setCoordinates(Coordinates coordinates) {
        this.coordinates = coordinates;
    }

    public List<NodeMetadata> getNodeList() {
        return nodeList;
    }

    public void setNodeList(List<NodeMetadata> nodeList) {
        this.nodeList = nodeList;
    }

    @Override
    public void buildOverlay(List<NodeMetadata> nodeList) {
        this.setNodeList(nodeList);
        this.setCoordinates(new Coordinates(nodeList.size(), this.getDimension()));
    }

    public List<NodeMetadata> generateNodeList(List<Integer> nodeIdList) {
        List<NodeMetadata> ret = new ArrayList<>();
        for (int id : nodeIdList) {
            NodeMetadata nodeMetadata = this.findServerById(id);
            // NodeMetadata nodeMetadata = this.nodeList.get(id - 1);
            // if (id != nodeMetadata.getId()) {
            //     DebugTool.write("[VectorOverlay] generateNodeList()  want send to " + id + " but send to " + nodeMetadata.getId());
            // }
            if (nodeMetadata == null) {
                LOGGER.info("[VectorOverlay] can not find node {}. ", nodeMetadata.getId());
                DebugTool.write("[VectorOverlay] can not find node " + nodeMetadata.getId() + ".");
                continue;
            }
            LOGGER.info("[VectorOverlay] Attempting to send to node {} ({}:{}).",
                    nodeMetadata.getId(), nodeMetadata.getAddress(), nodeMetadata.getPort());
            ret.add(nodeMetadata);
        }
        return ret;
    }

    @Override
    public List<NodeMetadata> calculateNodesToSync(AbstractMessage message, NodeMetadata source, NodeMetadata current) {
        LOGGER.info("[VecotrOverlay] calculateNodesToSync() called. sourceId = {}, currentId = {}", source.getId(), current.getId());

        // List<Integer> idList = this.getCoordinates().getList(message.getLastUpdateServerId(), current.getId());
        List<Integer> idList = this.getCoordinates().getList(source.getId(), current.getId());
        if (idList.equals(null)) {
            // Leaf node
            LOGGER.info("[VecotrOverlay] Error node Id.");
            return new ArrayList<>();
        } else {
            // List<NodeMetadata> ret = new ArrayList<>();
            // for (int id : idList) {
            //     NodeMetadata nodeMetadata = this.getNodeList().get(id);
            //     LOGGER.info("[VectorOverlay] Attempting to send to node {} ({}:{}).",
            //             nodeMetadata.getId(), nodeMetadata.getAddress(), nodeMetadata.getPort());
            //     ret.add(nodeMetadata);
            // }
            return this.generateNodeList(idList);
        }
    }

    @Override
    public List<NodeMetadata> calculateNodesToCollectAck(AbstractMessage message) {
        return this.getNodeList();
    }

    @Override
    public int getMaxLoad() {
        return Integer.MAX_VALUE;
    }

    public NodeMetadata findServerById(int serverId) {
        for (NodeMetadata nodeMetadata : this.getNodeList()) {
            if (nodeMetadata.getId() == serverId) {
                return nodeMetadata;
            }
        }
        return null;
    }
}
